/** @file    GardenerProgram.cpp
 *  @author  Alessandro Polo
 *  @version $Id: GardenerProgram.cpp 2894 2010-08-12 02:40:09Z alex $
 *  @brief
 * File containing methods for the GardenerProgram class.
 * The header for this class can be found in GardenerProgram.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/

 #include "GardenerProgram.h"
 #include <core/ObjectAllocator.h>
 #include <core/DataModels.h>
 #include <core/Utilities.h>
 #include <framework/building/GardenZone.h>
 #include <stdlib.h>
 #include <time.h>

#ifdef WOSH_PERSISTENCE_QT
 #include <framework/persistence/qt/EncodersXmlQt_Core.h>
#endif // WOSH_PERSISTENCE_QT


using namespace std;
namespace wosh {
 namespace services {

 WOSH_REGISTER(wosh::services::GardenerProgramTask, "", 1.0, _static_GardenerProgramTask )
 WOSH_REGISTER(wosh::services::GardenerProgram, "wosh::Object", 1.0, _static_GardenerProgram )

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

GardenerProgramTask::GardenerProgramTask() {
	clearLimits();
 }

GardenerProgramTask::~GardenerProgramTask() {
 }

void GardenerProgramTask::clearLimits() {
	this->time_min = 0;
	this->time_max = 0;
	this->time_period = 0;
	this->water_level = 0;
 }

Message* GardenerProgramTask::createZoneMessage( short action ) const {
	Message* message = new Message();
	message->setDestination(targetGardenZone);
	message->setDestinationBus(_Bus_Building);
	Request* request = NULL;
	switch(action) {
		case 0:
			request = new Request(_GardenZone_METHOD_close);
			break;
		case 1:
			request = new Request(_GardenZone_METHOD_open);
			break;
		default:
			break;
	 }
	message->setContent(request);
	return message;
 }

long GardenerProgramTask::calculateTaskTime() {
	this->current_TaskTime = 0;

//TODO

	if ( this->time_min != 0 && this->current_TaskTime < this->time_min )
		this->current_TaskTime = this->time_min;
	if ( this->time_max != 0 && this->current_TaskTime > this->time_max )
		this->current_TaskTime = this->time_max;

	return this->current_TaskTime;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

GardenerProgram::GardenerProgram()
	: ThreadImpl(NULL,"GardenerProgram"), Object() {
	this->uid = Utilities::randomLong();
	this->uid = _GET_Abs(this->uid);
	Object::setName("GardenerProgram", this->uid);
	Log.setContext(Object::getName());
	Log.setIndent( 5 );
	Log.setLevel( LOG_INFO );
Log.setLevel( LOG_DEBUG );

	this->enabled = true;
	this->type = TYPE_UNKNOWN;
	this->priority = 0;

	this->next_interrupt = 0;
	this->modifier_event_key = "";
	this->def_end_time = false;

	this->listener = NULL;
	this->cTask = NULL;
 }

void GardenerProgram::setName( const std::string& value ) {
	Object::setName(value);
	Log.setContext(Object::getName());
 }


//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////// RUN THREAD

bool GardenerProgram::initializingThread() {
	if ( this->listener == NULL )
		return false;
	return true;
 }

void GardenerProgram::runThread() {
	Log(LOG_DEBUG, "runThread() : Entering Loop.." );

	this->tasks.transactionBeginWrite();
	this->cTask = NULL;
	this->tasks.transactionEnd();

	if ( this->listener != NULL )
		this->listener->onProgramStarted(this);

	while(this->running) {
		ThreadImpl::sleepForMSec(1000);
		setThreadAlive();

		this->tasks.transactionBeginRead();
		Log(LOG_DEBUG, "runThread() : Selecting next Task [current=%p]..", this->cTask );
		selectNextTask_();
		if ( this->cTask == NULL ) {
			// error or finished
			this->tasks.transactionEnd();
			break;
		 }
		else {
			this->cTask->calculateTaskTime();
			startTask(this->cTask);
			waitTask(this->cTask, &this->running);
			stopTask(this->cTask);
			this->tasks.transactionEnd();
		 }
	 }

	this->tasks.transactionBeginWrite();
	this->cTask = NULL;
	this->tasks.transactionEnd();

	if ( this->listener != NULL )
		this->listener->onProgramFinished(this);

	Log(LOG_DEBUG, "runThread() : Exiting Loop.." );
 }

////////////////////////////////////////////////////////////////////////////////////////////////// RUN THREAD
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT GardenerProgram::start() {
	if ( isActive() ) {
		Log(LOG_CRITICAL, ":start() : Already Active!" );
		return WRET_ERR_ILLEGAL_USE;
	 }
	if ( this->listener == NULL ) {
		Log(LOG_CRITICAL, ":start() : NULL listener!" );
		return WRET_ERR_INTERNAL;
	 }
	Log(LOG_INFO, "start() : Starting Thread.." );
	return startThread(30000);
 }

WRESULT GardenerProgram::stop() {
	if ( !isActive() ) {
		Log(LOG_CRITICAL, ":stop() : Not Active!" );
		return WRET_ERR_ILLEGAL_USE;
	 }
	Log(LOG_INFO, "stop() : Stopping Thread.." );
	return quitThread(30000);
 }

WRESULT GardenerProgram::pause( unsigned long seconds ) {

	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT GardenerProgram::selectNextTask_() {
	if ( this->cTask == NULL ) {
		// must be the first
		this->cTask = this->tasks.frontItem();
		Log(LOG_VERBOSE, "selectNextTask_() : Selected item#0 [%p]", this->cTask );
		return WRET_OK;
	 }
	int index = this->tasks.indexOf(this->cTask);
	if ( index < 0 ) {
		Log(LOG_CRITICAL, ":selectNextTask_() : Current Task not found in list!" );
		this->cTask = NULL;
		return WRET_ERR_INTERNAL;
	 }
	if ( index >= this->tasks.size() ) {
		Log(LOG_INFO, ":selectNextTask_() : EndOfList[%d] reached.", index );
		this->cTask = NULL;
		return WRET_ERR_ILLEGAL_USE;
	 }
	this->cTask = this->tasks.itemAt(index+1);
	Log(LOG_VERBOSE, ":selectNextTask_() : Selected #%d [%p]", index+1, this->cTask );
	return WRET_OK;
 }

WRESULT GardenerProgram::startTask( GardenerProgramTask* task ) {
	if ( task == NULL ) return WRET_ERR_PARAM;
	if ( this->listener == NULL ) {
		Log(LOG_CRITICAL, ":startTask(%p) : NULL Listener!", task );
		return WRET_ERR_INTERNAL;
	 }
	Log(LOG_VERBOSE, ":startTask(%p) : Forwarding message [%s]", task, task->getZone().toString().c_str() );
	this->listener->postMessageForZone( this->cTask->createZoneMessage(1), this );
	return WRET_OK;
 }

WRESULT GardenerProgram::stopTask( GardenerProgramTask* task ) {
	if ( task == NULL ) return WRET_ERR_PARAM;
	if ( this->listener == NULL ) {
		Log(LOG_CRITICAL, ":stopTask(%p) : NULL Listener!", task );
		return WRET_ERR_INTERNAL;
	 }
	Log(LOG_VERBOSE, ":stopTask(%p) : Forwarding message [%s]", task, task->getZone().toString().c_str() );
	this->listener->postMessageForZone( this->cTask->createZoneMessage(0), this );
	return WRET_OK;
 }

WRESULT GardenerProgram::waitTask( GardenerProgramTask* task, bool* semaphore ) {
	if ( task == NULL ) return WRET_ERR_PARAM;
	long seconds = task->getTaskTime();
	Log(LOG_VERBOSE, ":waitTask(%p) : Waiting %s [%s]", task, Utilities::getTimeSpan(seconds*1000).c_str(), task->getZone().toString().c_str() );
	ThreadImpl::sleepWhile(this, seconds*1000, 500, semaphore);
	// save zone stats
	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT GardenerProgram::setStartSettings( int hour, int min, bool end_time, const std::string& modifier_event ) {

	time_t rawtime;
	struct tm* timeinfo;
	time ( &rawtime );
	timeinfo = localtime ( &rawtime );
	if ( timeinfo->tm_hour > hour ) {
		Log(LOG_VERBOSE, ":setStartSettings(%d,%d, %i,%s) : Postponed (tomorrow)", hour, min, end_time, modifier_event.c_str() );
		++timeinfo->tm_mday; // tomorrow
	 }
	else if ( timeinfo->tm_hour == hour ) {
		if ( timeinfo->tm_min > min ) {
			Log(LOG_VERBOSE, ":setStartSettings(%d,%d, %i,%s) : Postponed (tomorrow)", hour, min, end_time, modifier_event.c_str() );
			++timeinfo->tm_mday; // tomorrow
		 }
	 }
	timeinfo->tm_hour = hour;
	timeinfo->tm_min = min;
	rawtime = mktime( timeinfo );

	if ( end_time ) {
		unsigned long rewind_s = getProjectedProgramLength();
		Log(LOG_VERBOSE, ":setStartSettings(%d,%d, %i,%s) : Rewinding %ld seconds", hour, min, end_time, modifier_event.c_str(), rewind_s );
		rawtime -= rewind_s;
	 }
	this->def_end_time = end_time;
	this->next_interrupt = rawtime;
	this->modifier_event_key = modifier_event;
	Log(LOG_INFO, ":setStartSettings(%d,%d, %i,%s) : Next Interrupt @ %s", hour, min, end_time, modifier_event.c_str(), Utilities::getTimeStampUTF(this->next_interrupt).c_str() );

	setNextStartTime(this->next_interrupt);

	return WRET_OK;
 }

void GardenerProgram::setNextStartTime( int64 time ) {
	this->next_interrupt = time;
	if ( this->listener == NULL ) {
		Log(LOG_CRITICAL, ":setNextStartTime(%"PRId64") : NULL Listener!", time );
		return;
	 }
	this->listener->onProgramReSchedule(this);
 }

void GardenerProgram::evaluateWeatherUpdate( const wosh::xtended::WeatherInfo* updated_info, double similarity ) {
	if ( updated_info == NULL ) return;

	if ( this->modifier_event_key == "sun-rise" ) {
		unsigned long sun_rise = updated_info->getSunRise();// seconds after midnight
		int hours = (unsigned long)(sun_rise/3600);
		sun_rise = sun_rise%3600;
		int mins = (unsigned long)(sun_rise/60);

		setStartSettings(hours, mins, this->def_end_time, this->modifier_event_key);
	 }

 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

unsigned long GardenerProgram::getProjectedProgramLength() const {
	unsigned long seconds = 0;	
	tGardenerProgramTaskList::ConstIterator it;
	for ( it=this->tasks.begin(); it!=this->tasks.end(); ++it ) {
		if ( *it == NULL ) continue;
		seconds += (*it)->getTaskTime();
	 }
	return seconds;
 }

DataFieldCollector<GardenerProgram>* GardenerProgram::getDataFields() {
	DataFieldCollector<GardenerProgram>* fields = new DataFieldCollector<GardenerProgram>();
	fields->add<int64, Variant>(&GardenerProgram::getID, "ID", 0);
	fields->add<const std::string&, Variant>(&GardenerProgram::getName, "Name", 1);
	fields->add<bool, Variant>(&GardenerProgram::isEnabled, "Enabled", 2);
	fields->add<bool, Variant>(&GardenerProgram::isActive, "Active", 3);
	fields->add<long, Variant>(&GardenerProgram::getPriority, "Priority", 4);
	fields->add<PROGRAM_TYPE, Variant>(&GardenerProgram::getProgramType, "ProgramType", 5);

	fields->add<int64, Variant_DT>(&GardenerProgram::getNextStartTime, "NextStartTime", 4);
	fields->add<unsigned long, Variant_TS>(&GardenerProgram::getProjectedProgramLength, "ProjectedProgramLength", 4);

	return fields;
 }

 }; // namespace services

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

#ifdef WOSH_PERSISTENCE_QT

 namespace persistence {
  namespace qt {

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

 WOSH_ENCODER_XML_Qt_BIND(wosh::persistence::qt::EncoderXml_Qt_Services_GardenerProgram, "wosh::services::GardenerProgram", 0.0, _static_EncoderXml_Qt_Services_GardenerProgram )

WRESULT EncoderXml_Qt_Services_GardenerProgram::serializeTo( const wosh::services::GardenerProgram* object, QDomDocument& doc, QDomElement& contNode, int encoder_options ) {
	if ( object == NULL ) return WRET_ERR_PARAM;
	WRESULT ret = EncoderXml_Qt_Object::serializeTo(object, doc, contNode, encoder_options);
	EncoderBaseXmlQt::serializeBool( "enabled", object->isEnabled(), doc, contNode, encoder_options );
	EncoderBaseXmlQt::serializeBool( "active", object->isActive(), doc, contNode, encoder_options );
	EncoderBaseXmlQt::serializeLong( "priority", object->getPriority(), doc, contNode, encoder_options );

	QDomElement tasksNode = doc.createElement("tasks"); contNode.appendChild(tasksNode);
	ret += EncoderBaseXmlQt::serializeListT<wosh::services::GardenerProgramTask,EncoderXml_Qt_Services_GardenerProgramTask>( object->getTasks(), doc, tasksNode, encoder_options );

	return ret;
 }

WRESULT EncoderXml_Qt_Services_GardenerProgram::deserializeFrom( wosh::services::GardenerProgram* object, const QDomElement& contNode ) {
	if ( object == NULL ) return WRET_ERR_PARAM;
	WRESULT ret = EncoderXml_Qt_Object::deserializeFrom(object, contNode);
	object->setEnabled( EncoderBaseXmlQt::deserializeBool( "enabled", true, contNode ) );
	object->setPriority( EncoderBaseXmlQt::deserializeLong( "priority", 0, contNode ) );
	object->setProgramType( (wosh::services::GardenerProgram::PROGRAM_TYPE)EncoderBaseXmlQt::deserializeLong( "program-type", 0, contNode ) );

	QDomElement node_schedule = contNode.namedItem("schedule").toElement();
	if ( !node_schedule.isNull() ) {
		int end_time = EncoderBaseXmlQt::deserializeBool( "end-time", false, node_schedule );
		int hour = EncoderBaseXmlQt::deserializeLong( "hour", -1, node_schedule );
		int min = EncoderBaseXmlQt::deserializeLong( "min", -1, node_schedule );
		std::string modifier_event = "";
		QDomElement node_modifiers = node_schedule.namedItem("modifiers").toElement();
		if ( !node_modifiers.isNull() )
			modifier_event =EncoderBaseXmlQt::deserializeString( "event", "", node_modifiers );
		object->setStartSettings( hour, min, end_time, modifier_event );
	 }

	QDomElement tasksNode = contNode.namedItem("tasks").toElement();
	if ( !tasksNode.isNull() ) {
		ret += EncoderBaseXmlQt::deserializeListT<wosh::services::GardenerProgramTask,EncoderXml_Qt_Services_GardenerProgramTask>( object->getTasks(), tasksNode );
	 }
	return ret;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

 WOSH_ENCODER_XML_Qt_BIND(wosh::persistence::qt::EncoderXml_Qt_Services_GardenerProgramTask, "wosh::services::GardenerProgramTask", 0.0, _static_EncoderXml_Qt_Services_GardenerProgramTask )

WRESULT EncoderXml_Qt_Services_GardenerProgramTask::serializeTo( const wosh::services::GardenerProgramTask* object, QDomDocument& doc, QDomElement& contNode, int encoder_options ) {
	if ( object == NULL ) return WRET_ERR_PARAM;
	EncoderBaseXmlQt::serializeString( "zone", object->getZone().toString(), doc, contNode, true );
	QDomElement limitsNode = doc.createElement("limits"); contNode.appendChild(limitsNode);
	QDomElement timeNode = doc.createElement("time"); limitsNode.appendChild(timeNode);
	EncoderBaseXmlQt::serializeLong( "min", object->getLimitTimeMin(), doc, timeNode, encoder_options );
	EncoderBaseXmlQt::serializeLong( "max", object->getLimitTimeMax(), doc, timeNode, encoder_options );
	EncoderBaseXmlQt::serializeLong( "water-level", object->getLimitWaterLevel(), doc, limitsNode, encoder_options );
	EncoderBaseXmlQt::serializeLong( "period", object->getLimitTimePeriod(), doc, limitsNode, encoder_options );
	return WRET_OK;
 }

WRESULT EncoderXml_Qt_Services_GardenerProgramTask::deserializeFrom( wosh::services::GardenerProgramTask* object, const QDomElement& contNode ) {
	if ( object == NULL ) return WRET_ERR_PARAM;

	QDomElement uri_node = contNode.namedItem("zone").toElement();
	if ( !uri_node.isNull() )
		EncoderXml_Qt_URI::deserializeFrom(&object->getZone(), uri_node );
	else if ( contNode.hasAttribute("zone") )
		object->getZone().fromString( contNode.attribute("zone").toLatin1().data() );

	QDomElement limitsNode = contNode.namedItem("limits").toElement();
	if ( !limitsNode.isNull() ) {
		QDomElement timeNode = limitsNode.namedItem("time").toElement();
		if ( !timeNode.isNull() ) {
			object->setLimitTimeMin( EncoderBaseXmlQt::deserializeLong( "min", 0, timeNode ) );
			object->setLimitTimeMax( EncoderBaseXmlQt::deserializeLong( "max", 0, timeNode ) );
		 }
		object->setLimitWaterLevel( EncoderBaseXmlQt::deserializeLong( "water-level", 0, limitsNode ) );
		object->setLimitTimePeriod( EncoderBaseXmlQt::deserializeLong( "period", 0, limitsNode ) );
	 }
	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

  }; // namespace qt
 }; // namespace persistence

#endif // WOSH_PERSISTENCE_QT


}; // namespace wosh
